#include <stdlib.h>
#include <GL/glfw.h>
#include "Framework.h"
#include "Common/Algebra/Vec2.h"
#include "Input/ButtonState.h"
#include "Input/KeyboardKey.h"
#include "Input/KeyboardModifier.h"
#include "Input/Keyboard.h"
#include "Input/MouseButton.h"
#include "Input/Mouse.h"
#include "Input/EventArgs/KeyEventArgs.h"
#include "Input/EventArgs/MouseButtonEventArgs.h"
#include "Input/EventArgs/MouseMoveEventArgs.h"
#include "Input/EventArgs/MouseScrollEventArgs.h"

using namespace cmn;
using namespace input;

namespace core {

	void GLFWCALL ResizeCallback( int width, int height );
	int GLFWCALL CloseCallback();
	void GLFWCALL RefreshCallback();
	void GLFWCALL KeyCallback( int key, int state );
	void GLFWCALL MouseButtonCallback( int button, int state );
	void GLFWCALL MousePosCallback( int x, int y );
	void GLFWCALL MouseWheelCallback( int pos );

	void FillMouseState( Mouse& mouse );
	void FillKeyboardState( Keyboard& keyboard );

	ButtonState ToButtonState( int glfwButtonState );
	Vec2 ConvertMousePos( int x, int y );
	MouseButton ConvertMouseButton( int button );
	KeyboardKey ConvertKeyboardKey( int key );

	Framework::Framework() {
		if( !glfwInit() ) exit( EXIT_FAILURE );
		m_instance = this;
	}

	Framework::~Framework() {
		m_instance = 0;
		glfwTerminate();
	}

	float Framework::GetTime() {
		return float( glfwGetTime() );
	}

	Window* Framework::GetWindow() const {
		return m_window;
	}

	void Framework::SetWindow( Window* window ) {
		m_window = window;

		glfwSetWindowSizeCallback( ResizeCallback );
		glfwSetWindowCloseCallback( CloseCallback );
		glfwSetWindowRefreshCallback( RefreshCallback );

		glfwSetKeyCallback( KeyCallback );
		glfwSetMouseButtonCallback( MouseButtonCallback );
		glfwSetMousePosCallback( MousePosCallback );
		glfwSetMouseWheelCallback( MouseWheelCallback );
	}

	void GLFWCALL ResizeCallback( int width, int height ) {
		Size size = Size( width, height );
		Framework::Instance()->GetWindow()->Resized(size);
	}

	int GLFWCALL CloseCallback() {
		Framework::Instance()->GetWindow()->Closed();
		return GL_TRUE;
	}

	void GLFWCALL RefreshCallback() {
		Framework::Instance()->GetWindow()->Refreshed();
	}

	void GLFWCALL KeyCallback( int key, int state ) {
		Keyboard keyboard;
		FillKeyboardState( keyboard );

		KeyEventArgs eventArgs( keyboard, int( ConvertKeyboardKey( key ) ) );

		if( state == GLFW_PRESS )
			Framework::Instance()->GetWindow()->KeyPressed( eventArgs );
		if( state == GLFW_RELEASE )
			Framework::Instance()->GetWindow()->KeyReleased( eventArgs );
	}

	void GLFWCALL MouseButtonCallback( int button, int state ) {
		Keyboard keyboard;
		FillKeyboardState( keyboard );

		Mouse mouse;
		FillMouseState( mouse );

		MouseButtonEventArgs eventArgs( mouse, keyboard, ConvertMouseButton( button ) );

		if( state == GLFW_PRESS )
			Framework::Instance()->GetWindow()->MouseButtonPressed( eventArgs );
		if( state == GLFW_RELEASE )
			Framework::Instance()->GetWindow()->MouseButtonReleased( eventArgs );	}

	void GLFWCALL MousePosCallback( int x, int y ) {
		static Vec2 prevMousePos = Vec2::Zero();

		Keyboard keyboard;
		FillKeyboardState( keyboard );

		Mouse mouse;
		FillMouseState( mouse );
		Vec2 mousePos = mouse.GetPosition();
		MouseMoveEventArgs eventArgs( mouse, keyboard, mousePos - prevMousePos );

		Framework::Instance()->GetWindow()->MouseMoved( eventArgs );

		prevMousePos = mousePos;	}

	void GLFWCALL MouseWheelCallback( int pos ) {
		static int prevWheelPos = 0;

		Keyboard keyboard;
		FillKeyboardState( keyboard );

		Mouse mouse;
		FillMouseState( mouse );
		int wheelPos = mouse.GetWheelPosition();

		MouseScrollEventArgs eventArgs( mouse, keyboard, wheelPos - prevWheelPos );

		Framework::Instance()->GetWindow()->MouseScrolled( eventArgs );

		prevWheelPos = wheelPos;	}

	void FillMouseState( Mouse& mouse ) {
		mouse.SetButtonState( MouseButton::Left, ToButtonState( glfwGetMouseButton( GLFW_MOUSE_BUTTON_LEFT ) ) );
		mouse.SetButtonState( MouseButton::Middle, ToButtonState( glfwGetMouseButton( GLFW_MOUSE_BUTTON_MIDDLE ) ) );
		mouse.SetButtonState( MouseButton::Right, ToButtonState( glfwGetMouseButton( GLFW_MOUSE_BUTTON_RIGHT ) ) );

		int mouseX;
		int mouseY;
		glfwGetMousePos( &mouseX, &mouseY );
		mouse.SetPosition( ConvertMousePos( mouseX, mouseY ) );

		int wheelPos = glfwGetMouseWheel();
		mouse.SetWheelPosition( wheelPos );
	}

	void FillKeyboardState( Keyboard& keyboard ) {
        if( glfwGetKey( GLFW_KEY_LCTRL ) == GLFW_PRESS || glfwGetKey( GLFW_KEY_RCTRL ) == GLFW_PRESS )
			keyboard.SetModifierState( KeyboardModifier::Control, ButtonState::Pressed );

        if( glfwGetKey( GLFW_KEY_LSHIFT ) == GLFW_PRESS || glfwGetKey( GLFW_KEY_RSHIFT ) == GLFW_PRESS )
			keyboard.SetModifierState( KeyboardModifier::Shift, ButtonState::Pressed );

        if( glfwGetKey( GLFW_KEY_LALT ) == GLFW_PRESS || glfwGetKey( GLFW_KEY_RALT ) == GLFW_PRESS )
			keyboard.SetModifierState( KeyboardModifier::Alt, ButtonState::Pressed );
	}

	ButtonState ToButtonState( int glfwButtonState ) {
		return
			glfwButtonState == GLFW_PRESS
				? ButtonState::Pressed
				: ButtonState::Released;
	}

	Vec2 ConvertMousePos( int x, int y ) {
		static float matr[16];
		glGetFloatv( GL_VIEWPORT, matr );
		float width = matr[2];
		float height = matr[3];
		glGetFloatv( GL_MODELVIEW_MATRIX, matr );

		float scale = matr[10];
		Vec2 cam( -matr[12], -matr[13] );
		float worldX = ( ( x * 2.0f / width - 1.0f ) * width / height + cam.X ) / scale;
		float worldY = ( - ( y * 2.0f / height - 1.0f ) + cam.Y ) / scale;

		return Vec2( worldX, worldY );
	}

	MouseButton ConvertMouseButton( int button ) {
		return MouseButton( button );
	}

	KeyboardKey ConvertKeyboardKey( int key ) {
		return KeyboardKey( key );
	}
}
